<?php
declare(strict_types=1);

namespace src;

use src\interfaces\IDispatcher;

class Dispatcher implements IDispatcher
{
    /**
     * @var Container
     */
    protected $container;

    /**事件集合
     * @var array ['事件' => [监听者1, 监听者2]]
     */
    protected $listeners = [];

    /**
     * 通配符事件监听
     * @var array
     */
    protected $wildcards = [];
    protected $wildcardsCache = [];
    protected $queueResolver;

    /**
     * 创建一个调度器
     * @param Container|null $container
     */
    public function __construct(Container $container = null)
    {
        $this->container = $container ?: new Container;
    }

    /**
     *
     * 注册一个事件监听到调度器
     * @param $events
     * @param $listener
     * @return void
     */
    public function listen($events, $listener = null)
    {
        /*
         * event::listen(TestEvent:class, ['xxx', 'xxx']);
         * event::listen('xxx.*', ['xxx', 'xxx']);
         */
        foreach ((array)$events as $event){
            if(strpos($event,"*") !== false){
                $this->wildcards[$event][] = $this->makeListener($listener, true);
            }else{
                $this->listeners[$event][] = $this->makeListener($listener);
            }
        }
    }

    protected function makeListener($listener, $wildcard = false)
    {
        if (is_string($listener)){
            return $this->createClassListener($listener, $wildcard);
        }
        if (is_array($listener) && isset($listener[0]) && is_string($listener[0])){
            return $this->createClassListener($listener, $wildcard);
        }
        return function ($event, $payload) use ($listener, $wildcard) {
            if ($wildcard) {
                return $listener($event, $payload);
            }
            return $listener(...array_values($payload));
        };
    }

    protected function createClassListener($listener, $wildcard)
    {
        return function ($event, $payload) use ($listener, $wildcard) {
            if ($wildcard) {
                return call_user_func($this->createClassCallable($listener), $event, $payload);
            }
            $callable = $this->createClassCallable($listener);
            return $callable(...array_values($payload));
        };
    }

    protected function createClassCallable($listener)
    {
        [$class, $method] = is_array($listener)
            ? $listener
            : $this->parseClassCallable($listener);

        if (! method_exists($class, $method)) {
            $method = '__invoke';
        }
        $listener = $this->container->make($class);
        return [$listener, $method];
    }

    protected function parseClassCallable($listener)
    {
        if (explode($listener, "@") !== false){
            return explode($listener, "@", 2);
        }
        return [$listener, 'handle'];
    }

    public function hasListeners($eventName)
    {
        // TODO: Implement hasListeners() method.
    }

    public function subscribe($subscriber)
    {
        // TODO: Implement subscribe() method.
    }

    public function until($event, $payload = [])
    {
        // TODO: Implement until() method.
    }

    public function dispatch($event, $payload = [], $halt = false)
    {
        // TODO: Implement dispatch() method.
    }

    public function push($event, $payload = [])
    {
        // TODO: Implement push() method.
    }

    public function flush($event)
    {
        // TODO: Implement flush() method.
    }

    public function forget($event)
    {
        // TODO: Implement forget() method.
    }

    public function forgetPushed()
    {
        // TODO: Implement forgetPushed() method.
    }
}